//!
/**
 **************************************************************************************************************************************************************
 * 
 **************************************************************************************************************************************************************
 * @file main.cpp
 **************************************************************************************************************************************************************/
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#define _CRT_SECURE_NO_DEPRECATE


/**************************************************************************************************************************************************************
 * Includes
 **************************************************************************************************************************************************************/
#include <RBMain/RBCommon.h>

#include <RBApplication/RBWin32Application.h>
#include <RBApplication/Main.h>

#include <RBMain/RBCuller.h>
#include <RBD3D9Renderer/RBD3D9Renderer.h>
#include <RBMain/RBDAEReader.h>
#include <RBMain/RBDefaultRenderProcess.h>
#include <RBMain/RBGeometry.h>
#include <RBMain/RBHardwareBufferManager.h>
#include <RBMain/RBMath.h>
#include <RBMain/RBMatrix.h>
#include <RBMain/RBRenderProcess.h>
#include <RBMain/RBRenderTargetGroup.h>
#include <RBMain/RBRenderPassDebug.h>
#include <RBMain/RBSystem.h>
#include <RBMain/RBTexture.h>
#include <RBMain/RBTechnique.h>
#include <RBMain/RBVector.h>
#include <RBMain/RBTechniqueFactory.h>
#include <RBMain/RBVertexElement.h>

#include <RBD3D9Renderer/RBD3D9Texture.h>
#include <d3d9.h>

#include <RBRenderProcess/RBEffectFactory.h>


#define STR(s) #s
#define XSTR(s) STR(s)

#ifdef _DEBUG
#define PATH_TO_RB_LIB "../../../Lib/Debug"
#else
#define PATH_TO_RB_LIB "../../../Lib/Release"
#endif

#define COMMENT_LIB(Library) comment(lib, XSTR(Library##.lib))
#define COMMENT_RBLIB(Library) COMMENT_LIB(PATH_TO_RB_LIB ## "/" ## Library)

#pragma COMMENT_LIB(FCollada)
#pragma COMMENT_RBLIB(RBApplication)
#pragma COMMENT_RBLIB(RBGLRenderer)
#pragma COMMENT_RBLIB(RBD3D9Renderer)
#pragma COMMENT_RBLIB(RBMain)
//#pragma COMMENT_LIB(opengl32)

/**************************************************************************************************************************************************************
 * Defines
 **************************************************************************************************************************************************************/
#define APPLICATION_WIN32 0
#define APPLICATION_WX 1

#define APPLICATION_TYPE APPLICATION_WIN32

#include <fastdelegatebind.h>
#include "stb_image.h"

#pragma warning(push)
#pragma warning(disable: 4201)
template<typename Type, uint32 Count> class RBD3DColorData           { public: Type Data[Count];};
template<typename Type>  class RBD3DColorData <Type, 4> { public: union { struct { Type b; Type g; Type r; Type a;  }; Type Data[4]; }; };
typedef RBVector<uint8,   4, RBD3DColorData>  d3dbyte4;
typedef RBVector<uint16,   4, RBD3DColorData>  d3dhalf4;
#pragma warning(pop)


/**************************************************************************************************************************************************************
 * Classes implementation
 **************************************************************************************************************************************************************/

class RBSVT : public RBWin32Application
{
    RBDeclareRTTI(RBSVT, RBWin32Application)
public:

                                RBSVT  ();
    virtual                    ~RBSVT  ();

    virtual bool                Init                ();
    virtual void                OnIdle              ();

    RBCameraPtr                 m_spShadowCamera;

	bool						PreRenderCallback   (RBRenderProcess* pCurrentProcess, uint32 uiFrameID);
    virtual RBRenderProcessPtr  CreateRenderProcess (RBVisibilityManager* pMgr);

    void						PreProcessSVTGen(const RBVisibilityComponentArray& input, RBGeometryArray& output, RBRenderPass* pPass);
    void						PostProcessSVTGen(const RBGeometryArray& input, RBVisibilityComponentArray& output, RBRenderPass* pPass);

    void						PreProcessSVTUse(const RBVisibilityComponentArray& input, RBGeometryArray& output, RBRenderPass* pPass);
    void						PostProcessSVTUse(const RBGeometryArray& input, RBVisibilityComponentArray& output, RBRenderPass* pPass);

    void                        RelaxUVs            ();
    void                        ResetUVs            ();
    void                        ScaleUVs            ();
    void                        UpdateVB            ();

    void                        PostRenderSVT       (RBRenderPass* pSVTPass, uint32);

private:

    RBTexturePtr                m_spReadbackBuffer;
    RBTexturePtr                m_spPageTable;
    RBTexturePtr                m_spAtlas;
    RBPixelBufferPtr            m_spSVTRenderTarget;

    d3dbyte4*                   m_pData;
    RBEffect*                   m_spSVTGenTechnique;
    RBEffect*                   m_spSVTUseTechnique;
    std::vector<RBEffect*>      m_vEffects;
    uint32                      m_x;
    uint32                      m_y;

    uint32                      m_readbackX;
    uint32                      m_readbackY;

    int2                       m_freeAtlasSpaceStart;

};


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBSVT::RBSVT()
: RBWin32Application(1024,768, false)
{
    m_freeAtlasSpaceStart = int2(0,0);
}

#define IMAGE_PATH "Data/earth_mip3.jpg"
static const uint32 g_uiPageSize = 32;
static const int2 g_uiAtlasSize = int2(1024,1024);

/***********************************************************************************************************
 *
 ***********************************************************************************************************/
RBSVT::~RBSVT()
{
    m_spPageTable = 0;
    delete[]m_pData;
}

struct VertexPlane
{
    float3 Pos;
    float2 UVs;
};

RBGeometryPtr CreatePlane()
{
    VertexPlane* pPlaneVB = new VertexPlane[4];
    pPlaneVB[0].Pos = 80.0f*float3(-0.5,-0.25,0); pPlaneVB[0].UVs = float2(0,1);
    pPlaneVB[1].Pos = 80.0f*float3( 0.5,-0.25,0); pPlaneVB[1].UVs = float2(1,1);
    pPlaneVB[2].Pos = 80.0f*float3( 0.5, 0.25,0); pPlaneVB[2].UVs = float2(1,0);
    pPlaneVB[3].Pos = 80.0f*float3(-0.5, 0.25,0); pPlaneVB[3].UVs = float2(0,0);

    RBVertexBuffer* pVB = RBRenderer::GetSingleton()->GetHBMgr()->CreateVertexBuffer(RBHardwareBuffer::HBU_CPUWRITE, sizeof(VertexPlane), 4, pPlaneVB);

    RBVertexElement aElts[] = { RBVertexElement(0, RBVertexElement::VET_FLOAT3, RBVertexElement::VEU_POSITION),
                                RBVertexElement(0, RBVertexElement::VET_FLOAT2, RBVertexElement::VEU_TEXCOORD) };

    RBVertexDeclaration* pVDecl = RBRenderer::GetSingleton()->GetHBMgr()->CreateVertexDeclaration(aElts, 2);

    uint16* indices = new uint16[6];
    indices[0] = 0; indices[1] = 1; indices[2] = 2;
    indices[3] = 0; indices[4] = 2; indices[5] = 3;

    RBIndexBuffer* pIB = RBRenderer::GetSingleton()->GetHBMgr()->CreateIndexBuffer(RBHardwareBuffer::HBU_DEFAULT, RBIndexBufferType::IT_16BIT, 6, indices);

    delete[] indices;

    RBGeometry* pGeom = RBGeometry::CreateGeometry(pVB, pVDecl, pIB, RBGeometryPrimitiveType::TRIANGLELIST);
    pGeom->GetMaterial()->GetSampler(RBMaterial::ST_DIFFUSE).Texture = RBRenderer::GetSingleton()->GetHBMgr()->CreateTexture(IMAGE_PATH);
    return pGeom;
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
bool RBSVT::Init()
{
    if (!Super::Init())
    {
        return false;
    }

    m_spSceneRoot = new RBSceneNode;
    RBGeometryPtr spGeom = CreatePlane();


    spGeom->GetMaterial()->GetSampler(RBMaterial::ST_DIFFUSE).Texture = m_spAtlas;
    spGeom->GetMaterial()->GetSampler(RBMaterial::ST_SHADER0).Texture = m_spPageTable;

    m_spSceneRoot->AddChild(spGeom);
    m_spSceneRoot->UpdateTransformTree();

    OnSceneLoaded(m_spSceneRoot);

    m_spSVTGenTechnique = RBEffectFactory::GetSingleton()->RetrieveEffect("SVTGen");
    m_spSVTUseTechnique = RBEffectFactory::GetSingleton()->RetrieveEffect("SVTUse");

    return true;
}


/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::OnIdle()
{
    Super::OnIdle();
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
RBRenderProcessPtr RBSVT::CreateRenderProcess( RBVisibilityManager* pMgr )
{
    RBRenderProcessPtr spDefaultProcess = RBApplication::CreateRenderProcess(pMgr);

    int x,y,n;
    uint8* pRGBData = (uint8*)stbi_load(IMAGE_PATH, &x, &y, &n, 4);
    m_x = x;
    m_y = y;

    uint8* curRGBData = pRGBData;
    m_pData = new d3dbyte4[x*y];
    for (int32 i=0;i<x*y;++i)
    {
        m_pData[i] = d3dbyte4(curRGBData[2],curRGBData[1],curRGBData[0],1);
        curRGBData += 4;
    }
    stbi_image_free(pRGBData);

    m_spPageTable = RBRenderer::GetSingleton()->GetHBMgr()->CreateTexture(RBTexture::HBU_CPUWRITE, x/g_uiPageSize, y/g_uiPageSize, 
                                                                          PF_R8G8B8A8_UNORM, RBTextureType::SURFACE,1);

    RBPixelBuffer::MappedBuffer kPageTable = m_spPageTable->GetBuffer()->Map(RBHardwareBuffer::HBM_WRITE_ONLY);
    memset(kPageTable.Data, 0xFF, kPageTable.Pitch * 4 * y/g_uiPageSize );

    m_spPageTable->GetBuffer()->Unmap();

    m_spAtlas = RBRenderer::GetSingleton()->GetHBMgr()->CreateTexture(RBTexture::HBU_CPUWRITE, g_uiAtlasSize.x, g_uiAtlasSize.y, 
                                                                      PF_R8G8B8A8_UNORM, RBTextureType::SURFACE,1);

    m_readbackX = m_uiWindowWidth/4;
    m_readbackY = m_uiWindowHeight/4;

    m_spReadbackBuffer = RBRenderer::GetSingleton()->GetHBMgr()->CreateTexture(RBTexture::HBU_CPUREAD, m_readbackX,
                                                                               m_readbackY, PF_R8G8B8A8_UNORM, RBTextureType::SURFACE, 1, 0, 
                                                                               RBHardwareBuffer::MP_CPU);

    m_spSVTRenderTarget = RBRenderer::GetSingleton()->GetHBMgr()->CreateRenderTarget(RBHardwareBuffer::HBU_GPUWRITE, m_readbackX, m_readbackY,
                                                                                     PF_R8G8B8A8_UNORM, 0);



    RBRenderPass* pMainPass = spDefaultProcess->GetPassByName("MainPass");
    pMainPass->SetPreProcess(RBFastDelegateBind(&RBSVT::PreProcessSVTUse, this));
    pMainPass->SetPostProcess(RBFastDelegateBind(&RBSVT::PostProcessSVTUse, this));

    RBPixelBuffer* pZBuffer = RBRenderer::GetSingleton()->GetHBMgr()->CreateRenderTarget(RBTexture::HBU_DEPTHSTENCIL, m_readbackX,
                                                                                     m_readbackY, PF_D24_UNORM_S8_UINT,0);

    RBRenderPass* pSVTPass = new RBRenderPass(pMainPass->Culler, RBRenderTargetGroup::Create(m_spSVTRenderTarget, pZBuffer));
    pSVTPass->ClearColor = RBColorA(0,0,1,1);
    pSVTPass->SetPostRenderCallback(RBFastDelegateBind(&RBSVT::PostRenderSVT, this));
    pSVTPass->SetPreProcess(RBFastDelegateBind(&RBSVT::PreProcessSVTGen, this));
    pSVTPass->SetPostProcess(RBFastDelegateBind(&RBSVT::PostProcessSVTGen, this));

    spDefaultProcess->PrependPass(pSVTPass);

    return spDefaultProcess;
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::PostRenderSVT( RBRenderPass* pSVTPass, uint32 )
{
    m_freeAtlasSpaceStart = int2(0,0);

    pSVTPass->RenderTargetGroup->GetBuffer()->Blit(m_spReadbackBuffer->GetBuffer());

    // Analyze the needs of the reaback buffer
    RBPixelBuffer::MappedBuffer kReadbackBuffer = m_spReadbackBuffer->GetBuffer()->Map(RBHardwareBuffer::HBM_READ_ONLY);
    RBPixelBuffer::MappedBuffer kPageTable = m_spPageTable->GetBuffer()->Map(RBHardwareBuffer::HBM_READ_WRITE);
    RBPixelBuffer::MappedBuffer kAtlas = m_spAtlas->GetBuffer()->Map(RBHardwareBuffer::HBM_READ_WRITE);

    d3dbyte4* pRequestPages = (d3dbyte4*)kReadbackBuffer.Data;
    d3dbyte4* pPagesIndices = (d3dbyte4*) kPageTable.Data;
    d3dbyte4* pAtlas = (d3dbyte4*) kAtlas.Data;
    memset(pPagesIndices, 0xFF, kPageTable.Pitch * 4 * m_y/g_uiPageSize );
    memset(pAtlas, 0xFF, g_uiAtlasSize.x*g_uiAtlasSize.y*4 );

    // Find all the tiles requests
    d3dbyte4* pRequestRow = pRequestPages;
    for (uint32 y=0; y < m_readbackY; ++y)
    {
        d3dbyte4* currentRequest = pRequestRow;
        for (uint32 x=0; x < m_readbackX; ++x)
        {
            uint32 pageRequestIdx = currentRequest->r + currentRequest->g * kPageTable.Pitch/4;
            d3dbyte4* pageIdx = &pPagesIndices[pageRequestIdx];
            if (pageIdx->b != currentRequest->r ||
                pageIdx->a != currentRequest->g)
            {
                if (m_freeAtlasSpaceStart.y >= g_uiAtlasSize.y)
                {
                    // no more room: abort
                    break;
                }
                pageIdx->r = static_cast<uint8>(m_freeAtlasSpaceStart.x / g_uiPageSize);
                pageIdx->g = static_cast<uint8>(m_freeAtlasSpaceStart.y / g_uiPageSize);
                pageIdx->b = currentRequest->r;
                pageIdx->a = currentRequest->g;

                d3dbyte4* pAtlasStart = pAtlas + m_freeAtlasSpaceStart.x + m_freeAtlasSpaceStart.y * kAtlas.Pitch / 4;
                d3dbyte4* pSrcData = m_pData + currentRequest->r * g_uiPageSize + currentRequest->g * m_x * g_uiPageSize;
                for (uint32 row=0; row < g_uiPageSize; ++row)
                {
                    memcpy(pAtlasStart, pSrcData, g_uiPageSize*4);
                    pAtlasStart += kAtlas.Pitch/4;
                    pSrcData += m_x;
                }

                m_freeAtlasSpaceStart.x += g_uiPageSize;
                if (m_freeAtlasSpaceStart.x >= g_uiAtlasSize.x)
                {
                    m_freeAtlasSpaceStart.x -= g_uiAtlasSize.x;
                    m_freeAtlasSpaceStart.y += g_uiPageSize;
                }
            }
            ++currentRequest;
        }
        pRequestRow += kReadbackBuffer.Pitch/4;
    }

    m_spAtlas->GetBuffer()->Unmap();
    m_spPageTable->GetBuffer()->Unmap();
    m_spReadbackBuffer->GetBuffer()->Unmap();

}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::PreProcessSVTGen( const RBVisibilityComponentArray& input, RBGeometryArray& output, RBRenderPass* pPass )
{
    pPass->DefaultPreProcessGeometries(input, output);
    RBRenderPass::PushTechniques(output, m_vEffects, m_spSVTGenTechnique);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::PostProcessSVTGen( const RBGeometryArray& input, RBVisibilityComponentArray& output, RBRenderPass* pPass )
{
    RBRenderPass::PopTechniques(input, m_vEffects);
    m_vEffects.clear();
    pPass->DefaultPostProcessGeometries(input, output);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::PreProcessSVTUse( const RBVisibilityComponentArray& input, RBGeometryArray& output, RBRenderPass* pPass )
{
    pPass->DefaultPreProcessGeometries(input, output);
    RBRenderPass::PushTechniques(output, m_vEffects, m_spSVTUseTechnique);
}

/***********************************************************************************************************
 * 
 ***********************************************************************************************************/
void RBSVT::PostProcessSVTUse( const RBGeometryArray& input, RBVisibilityComponentArray& output, RBRenderPass* pPass )
{
    RBRenderPass::PopTechniques(input, m_vEffects);
    m_vEffects.clear();
    pPass->DefaultPostProcessGeometries(input, output);
}



MAIN_WIN32(RBSVT)

/**************************************************************************************************************************************************************/
